﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Linq.Expressions;

namespace DotNetRepository.LinqToObjects
{
    public sealed class LinqToObjectsRepository<TEntity> : BaseRepository<TEntity>, IEntityRepository<TEntity> where TEntity : class, IEntity
    {
        public static readonly LinqToObjectsRepository<TEntity> Instance = new LinqToObjectsRepository<TEntity>();
        private LinqToObjectsRepository(){}

        private readonly Dictionary<Guid,TEntity> _repository = new Dictionary<Guid, TEntity>();

        protected override IEntityRepository<TEntity> Self
        {
            get { return this; }
        }

        public override TEntity GetByID(Guid id)
        {
            if (id == Guid.Empty) return null;
            using(GuidLock.Lock(id))
            {
                if (_repository.ContainsKey(id)) 
                    return _repository[id];
                
                throw new ArgumentOutOfRangeException(
                    String.Format("There is no object of type {0} with ID {1}.", typeof(TEntity).Name, id));
            }
        }

        public override TEntity GetCachedByID(Guid id)
        {
            return GetByID(id);
        }

        public override bool IsDirty(TEntity entity)
        {
            return entity.Version == 0;
        }

        public override void Save(TEntity entity, object sender)
        {
            if (entity == null) return;

            lock (entity)
            {
                InitializeEntity(entity);

                entity.IncrementVersion();

                lock(_repository)
                    if (!_repository.ContainsKey(entity.ID))
                        _repository.Add(entity.ID, entity);

                if (HasEntityCollections)
                    SaveCollections(entity);
                if(HasChildEntitiesToPersist)
                    SaveChildEntities(entity);
                
                if (HasEnumerationsToNotify)
                    NotifyEntityUpdated(entity, sender);
            }
        }

        public override void Revert(TEntity entity)
        {
            return;
        }

        public override void Delete(TEntity entity, object sender)
        {
            lock (entity)
            {
                if (HasChildEntitiesToDelete)
                    DeleteChildEntities(entity);

                if (HasEntityCollections)
                    ClearEntityCollections(entity);

                _repository.Remove(entity.ID);

                NotifyEntityDeleted(entity,sender);
            }
        }

        public override bool IsCached(Guid id)
        {
            return _repository.ContainsKey(id);
        }

        public override bool Contains(TEntity entity)
        {
            return IsCached(entity.ID);
        }

        public override bool Any(Expression<Func<TEntity, bool>> predicate)
        {
            return _repository.Values.Any(predicate.Compile());
        }

        public override List<Guid> GetIDs(Expression<Func<TEntity, bool>> predicate)
        {
            return _repository.Values.Where(predicate.Compile()).Select(e => e.ID).ToList();
        }

        public override List<TEntity> Where(Expression<Func<TEntity, bool>> predicate)
        {
            return _repository.Values.Where(predicate.Compile()).ToList();
        }

        public override TEntity FirstOrDefault(Expression<Func<TEntity, bool>> filter)
        {
            return _repository.Values.Where(filter.Compile()).OrderBy(e => e.ID).FirstOrDefault();
        }

        public override TEntity LastOrDefault(Expression<Func<TEntity, bool>> filter)
        {
            return _repository.Values.Where(filter.Compile()).OrderByDescending(e => e.ID).FirstOrDefault();
        }

        public IEnumerable<TEntity> GetTable(ref object context)
        {
            return _repository.Values;
        }

        //public IEnumerable<T> GetTable<T>(ref object context) where T : class, IEntity
        //{
        //    return GetGenericRepository<T>().GetTable(ref context);
        //}

        //protected override Type GetGenericRepositoryType(IEntity entity)
        //{
        //    return typeof (LinqToObjectsRepository<>).MakeGenericType(new[] {entity.GetType()});
        //}

        //protected override Type GetGenericRepositoryType<T>()
        //{
        //    return typeof(LinqToObjectsRepository<>).MakeGenericType(new[] { typeof(T) });
        //}
    }
}
